//////////////////////////////////////////////
// Pool.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Includes ---------------------------------

// nkMemory
#include "../Allocators/Allocator.h"
#include "../Allocators/DefaultAllocator.h"

#include "../Deallocators/Deallocator.h"
#include "../Deallocators/DefaultDeallocator.h"

#include "../Log/LogManager.h"

#include "../Pointers/UniquePtr.h"

// Standards
#include <deque>
#include <functional>
#include <unordered_map>

/// Internals --------------------------------

namespace nkMemory
{
	template <typename T>
	struct PtrPoolItem
	{
			T* _object = nullptr ;

			PtrPoolItem<T>* _nextFreeItem = nullptr ;
	} ;
}

/// Class ------------------------------------

namespace nkMemory
{
	template <typename T>
	class PtrPool final
	{
		public :

			// Functions
			// Constructeur, prend ownership des allocators
			PtrPool (UniquePtr<Allocator<T>> allocator = makeUnique<DefaultAllocator<T>>(), UniquePtr<Deallocator<T>> deallocator = makeUnique<DefaultDeallocator<T>>()) noexcept
			:	_allocator (std::move(allocator)),
				_deallocator (std::move(deallocator)),
				_pool (),
				_firstFreeItem (nullptr),
				_flyingItems ()
			{
				// Nothing to do
			}

			PtrPool (const PtrPool&) noexcept = delete ;

			PtrPool (PtrPool&&) noexcept = default ;

			// Destructeur
			~PtrPool ()
			{
				// Deallocate pool
				for (const PtrPoolItem<T>& entry : _pool)
					_deallocator->deallocate(entry._object) ;
			}

		public :

			// Getters
			unsigned long long getPoolSize () const
			{
				return (unsigned long long)_pool.size() ;
			}

			unsigned long long getFreeItemsCount () const
			{
				return (unsigned long long)_pool.size() - (unsigned long long)_flyingItems.size() ;
			}

			unsigned long long getFlyingItemsCount () const
			{
				return (unsigned long long)_flyingItems.size() ;
			}

		public :

			// Object control
			T* getObject ()
			{
				if (!_firstFreeItem)
				{
					// Allocation nouvel item
					_pool.emplace_back(PtrPoolItem<T>()) ;

					_firstFreeItem = &_pool.back() ;
					_firstFreeItem->_object = _allocator->allocate() ;
				}

				T* result = _firstFreeItem->_object ;

				// On place dans la liste flying
				_flyingItems.emplace(result, _firstFreeItem) ;
				// On update la premier item libre
				_firstFreeItem = _firstFreeItem->_nextFreeItem ;

				// Et zou
				return result ;
			}

			// Release d'un object
			void releaseObject (T* object)
			{
				// On le remise dans la pool
				// Nos objects ont leur item associé directement
				typename std::unordered_map<T*, PtrPoolItem<T>*>::const_iterator searchResult = _flyingItems.find(object) ;

				if (searchResult == _flyingItems.end())
				{
					// Log
					std::string message ;
					message += "Asked a release of object not from pool. Beware." ;

					LogManager::getInstance()->log(message, "PtrPool") ;

					// Done
					return ;
				}

				// On reforme la chaîne
				PtrPoolItem<T>* item = searchResult->second ;

				item->_nextFreeItem = _firstFreeItem ;
				_firstFreeItem = item ;

				// Release depuis la map
				_flyingItems.erase(searchResult) ;
			}

		public :

			// Operators
			PtrPool& operator= (const PtrPool&) noexcept = delete ;

			PtrPool& operator= (PtrPool&&) noexcept = default ;

		private :

			// Attributes
			// Le callback d'allocations
			UniquePtr<Allocator<T>> _allocator ;
			UniquePtr<Deallocator<T>> _deallocator ;

			// La pool de track
			std::deque<PtrPoolItem<T>> _pool ;

			// Les items libres
			PtrPoolItem<T>* _firstFreeItem = nullptr ;

			// Les items en route quelque part
			std::unordered_map<T*, PtrPoolItem<T>*> _flyingItems ;
	} ;
}